#include<iostream>
#include <memory>

#include<GL/glew.h>
#include<GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "Scene.h"
#include "AssimpLoader.h"

using namespace std;

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);

const GLuint WIDTH = 800, HEIGHT = 600;
glm::vec3 camerapos{ 0.0f,0.0f,-300.0f };

GLFWwindow* initOpenGL() {
    glfwInit();

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    glfwSetKeyCallback(window, key_callback);
    glfwWindowHint(GLFW_SAMPLES, 4);

    glEnable(GL_MULTISAMPLE);
    glEnable(GL_DEPTH_TEST);
    glewExperimental = GL_TRUE;

    glewInit();
    return window;
}

std::shared_ptr<Scene> loadScene() {
    AssimpLoader loader;
    std::shared_ptr<Scene> scene = loader.Load("C:/Users/wxf/Desktop/test/test6.fbx");
    scene->mShader = Shader("./shader.vs", "./shader.frag");

    GLfloat vertices[] = {
        0.5f, -0.5f, 0.0f,   1.0f, 0.0f, 0.0f,
        -0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,
        0.0f,  0.5f, 0.0f,   0.0f, 0.0f, 1.0f
    };

    glGenVertexArrays(1, &scene->mVAO);
    glGenBuffers(1, &scene->mVBO);

    glBindVertexArray(scene->mVAO);
    glBindBuffer(GL_ARRAY_BUFFER, scene->mVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*scene->mMesh[0].mVertices.size(), scene->mMesh[0].mVertices.data(), GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 10 * sizeof(GLfloat)+4*sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 10 * sizeof(GLfloat) + 4 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 10 * sizeof(GLfloat) + 4 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 10 * sizeof(GLfloat) + 4 * sizeof(GLfloat), (GLvoid*)(10 * sizeof(GLfloat)));
    glEnableVertexAttribArray(3);

    glBindVertexArray(0);
    return scene;
}

glm::mat4 bonesmodel[2];

void getBoneMat(std::shared_ptr<Scene>scene,std::shared_ptr<Node> node,float frame,glm::mat4 parentMatrix) {

    auto trans = scene->mAnimation[0].getTranslateKey(frame).mValue;
    auto rotate = scene->mAnimation[0].getRotateKey(frame).mValue;

    auto transform =glm::rotate(glm::mat4(1.0f),glm::acos( rotate.w)*2.0f, glm::vec3(rotate.x, rotate.y, rotate.z));
    transform = glm::translate(glm::mat4(1.0f), glm::vec3(trans.x, trans.y, trans.z))*transform;
    auto temp = glm::transpose(node->mChildren[1]->mTransform)*
        transform;

    bonesmodel[0] = glm::transpose(node->mChildren[1]->mTransform)
        *glm::transpose(scene->mMesh[0].mBones[0].mOffsetMat);
    bonesmodel[1] = glm::transpose(node->mChildren[1]->mTransform)
        
        *transform
        *glm::transpose(scene->mMesh[0].mBones[1].mOffsetMat);

}


void display(GLFWwindow* window, std::shared_ptr<Scene> scene) {
    static uint32_t frame = 0;
    glfwPollEvents();
    _sleep(30);
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    scene->mShader.Use();
    glm::mat4 projection = glm::perspective(45.0f, (float)WIDTH / (float)HEIGHT, 0.1f, 1000.0f);
    glm::mat4 view = glm::lookAt(camerapos, glm::vec3( 0.0f,0.0f,0.0f ), glm::vec3{ 0.0f,1.0f,0.0f });
    glUniformMatrix4fv(glGetUniformLocation(scene->mShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(scene->mShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));

    // Draw the loaded model
    glm::mat4 model;
    //model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f)); // Translate it down a bit so it's at the center of the scene
    //model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f));	// It's a bit too big for our scene, so scale it down
    glUniformMatrix4fv(glGetUniformLocation(scene->mShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));

    getBoneMat(scene,scene->mNode, frame,glm::mat4());

    glUniformMatrix4fv(glGetUniformLocation(scene->mShader.Program, "bones[0]"), 1, GL_FALSE, glm::value_ptr(bonesmodel[0]));
    glUniformMatrix4fv(glGetUniformLocation(scene->mShader.Program, "bones[1]"), 1, GL_FALSE, glm::value_ptr(bonesmodel[1]));

    glBindVertexArray(scene->mVAO);
    glDrawArrays(GL_TRIANGLES, 0, scene->mMesh[0].mVertices.size());
    glBindVertexArray(0);
    
    glfwSwapBuffers(window);
    frame++;
    frame = fmod(frame,100);
}

int main()
{
    GLFWwindow* window = initOpenGL();

    auto scene = loadScene();

    while (!glfwWindowShouldClose(window))
    {
        display(window, scene);
    }

    glfwTerminate();
    return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key == GLFW_KEY_W)
    { 
        camerapos.x += 3.0;
    }
    if (key == GLFW_KEY_S)
    {
        camerapos.x -= 3.0;
    }
    if (key == GLFW_KEY_A)
    {
        camerapos.y += 3.0;
    }
    if (key == GLFW_KEY_D)
    {
        camerapos.y -= 3.0;
    }
}